	package erfgame.frontend.desktop.view.terrain.iso.handler;


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import erfgame.core.Point3D;
import erfgame.core.dao.ObjectDAO;
import erfgame.core.graphics.renderer.RectangleRenderer;
import erfgame.core.graphics.renderer.chain.RendererChain;
import erfgame.core.world.World;
import erfgame.core.world.terrain.BlockGridTiles;
import erfgame.core.world.terrain.BlockSurfaceImageFactory;
import erfgame.core.world.terrain.BlockSurfaceImages;
import erfgame.core.world.terrain.GridEdge;
import erfgame.core.world.terrain.GridRectangle;
import erfgame.core.world.terrain.TerrainUtils;
import erfgame.core.world.terrain.insertion.SurfaceRendererInsertion;

import erfgame.frontend.desktop.view.terrain.iso.IsometricTerrainView;

public class GridOutlineSelectionInteractionHandler extends BaseGridSelectionInteractionHandler {
	
	private ObjectDAO<Point3D, BlockSurfaceImages> bsiDAO;
	private ObjectDAO<Point, int[][][]> surfacesDAO;
	
	private Map<GridEdge, Collection<SurfaceRendererInsertion>> insertions;
	
	public GridOutlineSelectionInteractionHandler( 
			IsometricTerrainView view, 
			ObjectDAO<Point3D, BlockSurfaceImages> bsiDAO, 
			ObjectDAO<Point, int[][][]> surfacesDAO,
			World world, 
			ZLevelSelector zLevelSelector ) {
		super(view, zLevelSelector);
		this.bsiDAO = bsiDAO;
		this.surfacesDAO = surfacesDAO;
		this.insertions = new HashMap<GridEdge, Collection<SurfaceRendererInsertion>>();
	}
	
	@Override
	protected void highlight( GridRectangle selectionRectangle, int[] zRange ) {
		// work out the grid that contains it
		HashMap<GridEdge, Collection<SurfaceRendererInsertion>> insertions;
		insertions = new HashMap<GridEdge, Collection<SurfaceRendererInsertion>>( this.insertions );
		
		int gx = selectionRectangle.getWestX();
		int gy = selectionRectangle.getWestY();
		int neDimension = selectionRectangle.getNorthEastDimension();
		int seDimension = selectionRectangle.getNorthWestDimension();
		
		World world = view.getCurrentWorld();
		int gridDiagonalOn2 = world.getGridDiagonal() / 2;
		int viewZ = view.getBlockZ() * world.getBlockDepth();		
		// get the north-west grid coordinates (left to right)
		int[] cachedSurfaces = new int[gridDiagonalOn2];
		Point result = new Point();
		RectangleRenderer renderer = new RectangleRenderer( new Dimension(1, world.getWorldDepth()), Color.red );
		for( int dir=4; dir>0; ) {
			dir--;
			int dimension;
			int edgeIndex;
			int incx;
			int incy;
			int txadj;
			int tyadj;
			switch( dir ) {
			case 3:
				// ne
				dimension = neDimension;
				edgeIndex = BlockGridTiles.INDEX_NE;
				incx = 1;
				incy = -1;
				txadj = -gridDiagonalOn2;
				tyadj = -1;
				break;
			case 2:
				// se
				dimension = seDimension;
				edgeIndex = BlockGridTiles.INDEX_SE;
				incx = 1;
				incy = 1;
				txadj = 0;
				tyadj = -gridDiagonalOn2;
				break;
			case 1:
				// sw
				dimension = neDimension;
				edgeIndex = BlockGridTiles.INDEX_SW;
				incx = -1;
				incy = 1;				
				txadj = gridDiagonalOn2-1;
				tyadj = 0;
				break;
			case 0:
			default:
				// nw
				dimension = seDimension;
				edgeIndex = BlockGridTiles.INDEX_NW;
				incx = -1;
				incy = -1;				
				txadj = -1;
				tyadj = gridDiagonalOn2-1;
				break;
			}
			for( int i=dimension; i>=0; ) {
				i--;
				
				GridEdge edge = new GridEdge( gx, gy, edgeIndex );
				Collection<SurfaceRendererInsertion> edgeInsertions = this.insertions.remove( edge );
				if( edgeInsertions == null ) {
					edgeInsertions = new ArrayList<SurfaceRendererInsertion>( gridDiagonalOn2 );
					insertions.put( edge, edgeInsertions );
					int tx = TerrainUtils.getTx(gx, gy, world);
					int ty = TerrainUtils.getTy(gx, gy, world);
					int txfrom = tx + txadj;
					int tyfrom = ty + tyadj;
					int bx = txfrom >> world.getBlockWidthShift();
					int by = tyfrom >> world.getBlockHeightShift();
					if( bx >= 0 && bx < world.getBlocksX() && by >= 0 && by < world.getBlocksY() ) {
						int[][][] surfaces = this.surfacesDAO.retrieveObject(new Point(bx, by));
						int startlocaltx = txfrom & world.getBlockWidthMask();
						int startlocalty = tyfrom & world.getBlockHeightMask();
						int localtx = startlocaltx;
						int localty = startlocalty;
						for( int j=gridDiagonalOn2-1; j>0; ) {
							j--;
							// add in a temporary renderer
							int[] surface = surfaces[localtx][localty];
							int zIndex = Arrays.binarySearch(surface, viewZ);
							if( zIndex < 0 ) {
								zIndex = -(zIndex + 1);
							}
							if( (zIndex & 1) != 0 ) {
								zIndex++;
							}
							if( zIndex >= surface.length ) {
								zIndex = surface.length-2;
							}
							int tz;
							if( zIndex >= 0 ) {
								tz = surface[zIndex] - 1;
							} else {
								tz = -1;
							}
							cachedSurfaces[j] = tz;
							localtx += incx;
							localty += incy;
						}
						localtx = startlocaltx;
						localty = startlocalty;
						for( int j=gridDiagonalOn2-1; j>0; ) {
							j--;
							int currentTz = cachedSurfaces[j];
							
							if( currentTz >= 0 ) {
								int bz = currentTz / world.getBlockDepth();
								int localtz = currentTz % world.getBlockDepth();
								Point3D blockKey = new Point3D( bx, by, bz );
								BlockSurfaceImages bsis = bsiDAO.retrieveObject( blockKey );
								Rectangle bounds = new Rectangle( 
										localtx, 
										BlockSurfaceImageFactory.getPixelY( localty, localtz ), 
										1, 
										1
								);
								int depth = (world.getBlockHeight() - localty - 1)/2;
								SurfaceRendererInsertion insertion;
								insertion = new SurfaceRendererInsertion( 
										depth, 
										new RendererChain( renderer ),
										0, 
										0, 
										bounds, 
										bounds.y,
										blockKey 
								);
								// TODO : add all insertions at once
								bsis.addInsertion( insertion );
								edgeInsertions.add( insertion );
							}
							
							localtx += incx;
							localty += incy;
						}
					}					
				}
				if( i >= 0 ) {
					TerrainUtils.getGridCoordinates(gx, gy, edgeIndex, result);
					gx = result.x;
					gy = result.y;
				}
			}
		}
		
		for( Map.Entry<GridEdge, Collection<SurfaceRendererInsertion>> insertionsToRemove: this.insertions.entrySet() ) {
			GridEdge key = insertionsToRemove.getKey();
			insertions.remove(key);
			for( SurfaceRendererInsertion insertionToRemove : insertionsToRemove.getValue() ) {
				BlockSurfaceImages bsi = this.bsiDAO.retrieveObject(insertionToRemove.getBlockKey());
				bsi.removeRenderer(insertionToRemove);				
			}
		}
		this.insertions = insertions;
	}
	
	protected void clearHighlight() {
		for( Collection<SurfaceRendererInsertion> insertions : this.insertions.values() ) {
			for( SurfaceRendererInsertion insertion : insertions ) {
				BlockSurfaceImages bsi = this.bsiDAO.retrieveObject(insertion.getBlockKey());
				bsi.removeRenderer(insertion);
			}
		}
		this.insertions.clear();
	}
	
	protected void select( int x1, int y1, int z1, int x2, int y2, int z2 ) {
		
	}
	
}
